package EP::Visualization::TreemapGraphic;

use strict;

use EP::Visualization::Graphics;
use EP::Visualization::Hooks;

use PDL;
use PDL::NiceSlice;
use Data::UUID;

use GO::TermFinder;
use GO::AnnotationProvider::AnnotationParser;
use GO::OntologyProvider::OntologyParser;
use GO::Utils::General qw (CategorizeGenes);

our @ISA = qw ( EP::Visualization::Graphics );

########################################################
# This method sets the data of the treemap, that means #
#   - the tree data                                    #
#   - the heatmap data                                 # 
########################################################
sub setData {
	my $self = shift;
	my %params = @_;

	die "No heatmapdata supplied to TreemapGraphic"   if not exists $params{heatmap_data};
	die "No tree supplied to TreemapGraphic::setData" if not $params{data};
	die "No rows supplied to TreemapGraphic::setData" if not $params{rows};
	die "No species supplied to TreemapGraphic::setData" if not $params{species};
	
	my $heatmap_data = $params{heatmap_data};
	$self->{heatmap_data} = $heatmap_data;
	my @stats = ($heatmap_data->stats())[0,1,2];
	$self->{heatmap_data_stats} = \@stats;
	$self->read_in_tree ( $params{data} );
	$self->{rows}    = $params{rows};
	$self->{species} = $params{species};
	$self->{dataset} = $params{dataset};
	$self->{root_dataset} = $params{root_dataset};
	
	$self->initGO();
	return;
}

######################################################
# returns string identifying the object as a treemap #
######################################################
sub getType {
  return "treemap";
}


#########################
# returns treemap width #
#########################
sub getWidth {
	my $self = shift;
	return 700;
}

##########################
# returns treemap height #
##########################
sub getHeight {
	my $self = shift;
	return 500;
}

#########################
# returns control panel #
#########################
sub get_control_panel {
  my $self = shift;
  my $control_panel = new EP::Visualization::TreemapGraphic::ControlPanel ( control => $self );
  
  $self->{control_panel} = $control_panel;   
  return $control_panel;
}

#########################
# returns treemap depth #
#########################
sub get_depth {
	my $self = shift;
	if (defined($self->{depth})) {
		return $self->{depth}; 
	}
	else {
		die "I got no depth from treemap";
	}
}

#################################
# returns treemap initial depth #
#################################
sub get_init_depth {
	my $self = shift;
	if (defined($self->{initdepth})) {
		return $self->{initdepth};
	}
	else {
		die "I got no initial depth from treemap";
	}
	
}


####################################################
# produces the SVG elements of the treemap object. #
####################################################
sub to_svg {
	# params
	my $self = shift;
	my ( $svg1, $height, $width, $xposition, $yposition ) = @_;
	
	# vars
  my $BOUND_WIDTH       = $self->{boundwidth};
	my $readyToDraw       = $self->build_treemap($xposition, $yposition, $width, $height);
	my @readyToDraw       = sort { $a->[5] <=> $b->[5] } @$readyToDraw; # sort boxes by depth
	my $depth             = ($readyToDraw[$#readyToDraw][5]); # depth of the deepest node
	my $INITIAL_MAX_DEPTH = 0;
	if ($depth>5){
		$INITIAL_MAX_DEPTH = 5;
	}
	else {
		$INITIAL_MAX_DEPTH = $depth;
	}
	$self->{depth}        = $depth;
	$self->{initdepth}    = $INITIAL_MAX_DEPTH;
	#warn "depth             $depth";
	#warn "INITIAL_MAX_DEPTH $INITIAL_MAX_DEPTH";
	
	# start build
	my $svg = $svg1->svg ( width=> $width, 
												height=>$height,
												"viewBox" => "$xposition $yposition $width $height", 
												"id" => "treemap", 
												"style" => 'text-rendering:geometricPrecision;', # antialiasing
												"onload" => "initTreemap(evt)",
												"depth" => $self->{depth},
												"current_depth" => $self->{initdepth});
	
	$svg1->rect ( 'x' => $xposition, 
								'y' => $yposition, 
								height => $height, 
								width => $width, 
								fill => 'none', 
								stroke => 'black');


	# recall: box comprises: 0:x, 1:y, 2:w, 3:h, 4:rotated, 5:depth, 6:fill, 7:id 
	# Use depth in order to get the colours
	
	#TODO: think of sth smart to avoid a black hole or just sth from the white range
	my $steps_displayed = ($height/4)/2*$BOUND_WIDTH;
	my $colour_step = ( 255 / $depth );
	if ($depth>$steps_displayed){
		$colour_step = ( 255 / $steps_displayed);
	}
	my $colour      = 255;

	# draw boxes
	my $parentgroup;
	my $current_depth    = -1;
	my $group = $svg->group ("id" => "d".$current_depth);
	my $tooManyClusters  = 20000; #TODO :find good values here
	my $tooManyMembers   = 2000;
	my $notEnoughMembers = 5;
	my $i                = 0;
	
	for my $box_ref ( @readyToDraw ) {
		my @box = @$box_ref;
		if ($box[5] > $current_depth) {
			$current_depth = $box[5];
			$parentgroup = $group;
			if ($current_depth == $self->{initdepth}){
				$group = $parentgroup->group ("id" => "d".$current_depth, 'display' => 'none');
			} 
			else {
				$group = $parentgroup->group ("id" => "d".$current_depth);
			}
			$colour -= $colour_step;
		}
		
		#calculate fill
		my @fill;
		# compute colour only if we do not have too many clusters in treemap
		if (($#readyToDraw < $tooManyClusters) && ($box[2] > 1) && ($box[3] > 1)){
			@fill = @{$self->compute_color($box[8])};
		}
		else { #but only for the visible ones
			@fill = (127.5, 127.5, 127.5);
		}
		my $boxgroup = $group->group( transform => "translate(".$box[0]." ".$box[1].")");
		$boxgroup->rect ( 'x'      => 0, 
								'y'            => 0, 
								width          => $box[2],
								height         => $box[3], 
								stroke         => "rgb($colour, $colour, $colour)", 
								fill           => "rgb(".$fill[0].", ".$fill[1].", ".$fill[2].")", 
								'stroke-width' => 2*$BOUND_WIDTH+1, #+1 should cause a small overlap
								'onclick'      => 'handleWisely(evt)', 
								'id'=>$box[8] );

		if ($box[2] > 12 && $box[3] > 7){
			my $fontsize = $box[3]/7;
			$fontsize = ($box[2]/10) if (($box[2]/10) < ($box[3]/7));
			$boxgroup->text("x"          => ($box[2])/2,
										"y"            => ($box[3])/2,
										"font-family"  => "courier",
										"text-anchor"  => "middle",
										"font-size"    => $fontsize,
										"fill"         => "white" )->cdata ( $box[7]." members" );

			# get GO anntotation: 
			# - only if we do not have too many clusters,
			# - and a nice number of members in the cluster
			if (($#readyToDraw < $tooManyClusters) &&
					($box[7] < $tooManyMembers) &&
					($notEnoughMembers < $box[7])){

				my $go = $self->getGO($box[8]);
				if ($go){
					#warn $go;
					my @go = split("\n", $go);
					my $j = 1;
					foreach (@go){
					my ($term, $color) = split(/:/, $_);
					$boxgroup->text("x"          => ($box[2])/2,
												"y"            => ($box[3])/2 + $j*$fontsize,
												"font-family"  => "courier",
												"text-anchor"  => "middle",
												"font-size"    => $fontsize,
												"fill"         => $color )->cdata ( $term );
					$j++;
					}
				}
			}
		}
		$i++;
	}
	
	# define place for the prompts
	my $promptwidth  = 300;
	my $promptheight = 160;
	my $promptx     = ($width  - $promptwidth )/2;
	my $prompty     = ($height - $promptheight)/2;
	$svg1->group ( transform => "translate($promptx $prompty)", id => "prompt" );
	my $html_href = $EP::Config::h->{HTML_HREF};

	$svg->script()->cdata(<<__JS__);
	
	var svg     = document;
	var treemap = svg.getElementById("treemap");
	var toggle  = svg.getElementById("toggle");
	var annotLink =  svg.getElementById("annotLink");
	var goLink =  svg.getElementById("goLink");
	
	var clusters                = new Array();
	var selectedClusters        = new Array();
	var selectedClustersStrokes = new Array();
	
	var plots  = new Array();
	var thisparent = null;
	var name   = null;

	var PNG    = "heatmap";

/////////////
// functions
/////////////
	
	//////////////////////////////////
	// remove labels which are to wide
	//////////////////////////////////
	function initTreemap(evt) { 
		svgdoc = evt.getTarget().getOwnerDocument();
		
		textlabels = treemap.getElementsByTagName('text');

		for (j = 0; j<=2; j++){ //HACK why is this necessary???
			for (i = 0; i<textlabels.length; i++){

				label     = textlabels.item(i);
				textwidth = label.getComputedTextLength()+2;
				boxgroup  = label.getParentNode();
				siblings  = boxgroup.childNodes;
				rect      = siblings.item(1);
				rectwidth = rect.getAttribute('width');
				if (textwidth > rectwidth){
					boxgroup.removeChild(label);
				}
			}
		}
		
	}

	//////////////////////////////////
	//handle stuff, ie shiftclicks 
	//////////////////////////////////
	function handleWisely(evt) {
			unselectAll();
			removePNGs();
			select(evt);
			fetch_box_annotations(evt.target);
			fetch_box_png(evt.target);
	}
	
	function togglePNG(){
		if (PNG == "heatmap"){
			PNG = "lineplot";
			child = toggle.getElementsByTagName("image").item(0);
			child.setAttribute('xlink:href', "$html_href/static/images/lp.png");
			
			removePNGs();
			if (selectedClusters.length>0){
				add_alert(html_href + "/static/images/alert.svg", "prompt", "loading\\n lineplot now\\n");
				for (i = 0; i<selectedClusters.length; i++){
					cluster = selectedClusters[i];
					fetch_box_png(cluster);
				}
			}
		}
		else if (PNG == "lineplot"){
			PNG = "heatmap";
			child = toggle.getElementsByTagName("image").item(0);
			child.setAttribute('xlink:href', "$html_href/static/images/hm.png");
			
			removePNGs();
			if (selectedClusters.length>0){
				add_alert(html_href + "/static/images/alert.svg", "prompt", "loading\\n heatmap now\\n");
				for (i = 0; i<selectedClusters.length; i++){
					cluster = selectedClusters[i];
					fetch_box_png(cluster);
				}
			}
		}
		else {
			alert("Error: PNG was neither heatmap nor lineplot!");
		}
	}

	//function toggleHeatmaps() {
	//	if (plots.length>0){
	//		removePNGs();
	//	}
	//	else {
	//		annot.setText ( "loading heatmaps...\\nplease wait" );
	//		cl = getVisibleClusters();
	//		fetch_png_array("heatmap", cl);
	//	}
	//}

////////////////////
//  SAVE CLUSTERS
////////////////////

	
	function save_target_cluster(evt){
		object        = evt.target;
		var tclusters = new Array();
		tclusters.push(object);
		save_cluster_array(tclusters);
	}
	
	function saveVisibleClusters(evt){
		vclusters = getVisibleClusters(evt);
		save_cluster_array(vclusters);
	}
	
	//may be used somewhere else - be careful 
	function prompt_use_prompt(result,data,num_w) {
		if (result==true){
			name = data;
			cluster = selectedClusters[0];
			id      = cluster.getAttribute("id");// treeid, lh, rh
			arr_id  = id.split(':');
			var treeID = arr_id[0];
			var lhs    = arr_id[1];
			var rhs    = arr_id[2];
			tree_cluster_selections[treeID] = new Array();
			tree_cluster_selections[treeID][tree_cluster_selections[treeID].length] = new Array ( lhs, rhs, name);
			getURL ( cgi_href + "/ep_save_clusters.pl?clusters=" + clusters(tree_cluster_selections[treeID]) + ',1,1&tree_id=' + treeID, clusters_saved );
			add_alert( html_href + "/static/images/alert.svg", "prompt", 'Saving Cluster \\n"'+ name +'"!\\n\\n');
		}

		function clusters ( sel ) {
			var c = sel.join (":");
			return c;
		}

		function clusters_saved ( urlRequestStatus ) { 
			if ( urlRequestStatus.success ) {
				//alert ("Cluster saved!");
				//TODO alert here - how to get the name??
			}
		}
	}
	
	function save_and_prompt(){
		if (selectedClusters.length == 1){
			add_prompt ( html_href + "/static/images/prompt.svg", "prompt", "Please enter a name\\n for this cluster:\\n", 0 );
		}
		else {
			add_alert( html_href + "/static/images/alert.svg", "prompt", 'Select a Cluster \\n to save!\\n\\n');
		}
	}
	
	function save_cluster_array(clusterArray){
		cluster0 = clusterArray[0];//TODO only once
		id0      = cluster0.getAttribute("id");// treeid, lh, rh
		arr_id0  = id0.split(':');
		treeID   = arr_id0[0];
		tree_cluster_selections[treeID] = new Array();

		for (i = 0; i<clusterArray.length; i++){
			cluster = clusterArray[i];
			id      = cluster.getAttribute("id");// treeid, lh, rh
			arr_id  = id.split(':');
			var lhs = arr_id[1];
			var rhs = arr_id[2];
			//tree_cluster_selections[treeID][tree_cluster_selections[treeID].length] = new Array ( //lhs, rhs, "cluster "+i);
		}

		getURL ( cgi_href + "/ep_save_clusters.pl?clusters=" + clusters(tree_cluster_selections[treeID]) + ',1,1&tree_id=' + treeID, clusters_saved );

		function clusters ( sel ) {
			var c = sel.join (":");
			return c;
		}

		function clusters_saved ( urlRequestStatus ) { 
			if ( urlRequestStatus.success ) {
				//alert ("clusterArray.length + 'Clusters saved!");
				//browserEval ( "alert(clusters.length + 'Clusters saved!');" );
			}
		}
	}

/////////////////////////
// GET CLUSTERS
/////////////////////////

	function getVisibleClusters(){
		var visibleClusters = new Array();
		current_depth       = parseInt(treemap.getAttribute('current_depth'))-1;
		object_d            = svg.getElementById("d"+current_depth);
		children            = object_d.getChildNodes();
		for (i = 0; i<children.length; i++){
			group = children.item(i);
			groupmembers = group.getChildNodes();
			if (group.nodeName == "g" && !group.getAttribute("id")){
				rect = groupmembers.item(1);
				if (rect.nodeName == "rect" && rect.getAttribute("fill")){
					visibleClusters.push(rect);
				}
			}
		}
		return visibleClusters;
	}

	function getSelectedClusters(){
		return selectedClusters;
	}

	function showVisibleClustersHeatmaps(){
		vclusters = getVisibleClusters();
		fetch_heatmap_array(vclusters);
	}

/////////////////////
// FETCH ANNOTATIONS
/////////////////////

	function fetch_box_annotations(box){
		annot.setText("fetching annotations... \\nplease wait");
		thisparent = box.getParentNode();
		ids    = box.getAttribute('id');
		annotLink.setAttribute('xlink:href',  "$EP::Config::h->{CORE_HREF}/ep_get_genes_from_cluster.pl?ids=" + ids + "&giveme=" + "annotations");
		goLink.setAttribute('xlink:href',  "$EP::Config::h->{CORE_HREF}/ep_get_go.pl?ids=" + ids + "&giveme=" + "GO");

		getURL ( "$EP::Config::h->{CORE_HREF}/ep_get_genes_from_cluster.pl?ids=" + 
						ids + "&giveme=" + "annotations", genes );
		
		function genes ( data) {
			var string = '';

			if(data.success) {
				string = data.content;
				
				var array = string.split("\\n");
				var annotations = "";
				for (i=0; i<array.length;i++){
					annotations += array[i].slice(0, 23)+"\\n";
				}
				sum = array.length-1;
	
				// annotations
				if (sum == 1){
					annot.setText ( sum+" member\\n"  +  annotations);
				}
				else {
					annot.setText ( sum+" members\\n" +  annotations);
				}
			}
			else {
				alert("could not get data from $EP::Config::h->{CORE_HREF}/ep_get_genes_from_cluster.pl");
			}
		}
	}

/////////////////////
// FETCH PNG
/////////////////////

	function fetch_png_array(plottype,clusterArray){
	//alert ("fetch png array ");
	
		for (i = 0; i<clusterArray.length; i++){
			box = clusterArray[i];
			h = box.getAttribute("height");
			w = box.getAttribute("width");
			
			if (( w > 10) && (h > 10)){
				fetch_box_png(plottype,box);
			}
		}

	}


	function fetch_box_png(box){
		plottype = PNG;
		ids    = box.getAttribute('id');

		w = box.getAttribute('width');
		h = box.getAttribute('height');

		plot = getURL ( "$EP::Config::h->{CORE_HREF}/ep_get_genes_from_cluster.pl?ids=" + 
						ids + "&w=" + w + "&h=" + h + "&giveme=" + plottype, function ( data ) {
			if(data.success) {
			
				var string = data.content;
				var array  = string.split("\\n");
				var plot   = array[0];
				var id     = array[1];

				// put plot as new node in document
				var node   = parseXML(plot, svg);
				var thiscluster = document.getElementById ( id );
				
				parentgroup = thiscluster.getParentNode();
				translate = parentgroup.getAttribute('transform');
				array = translate.split("(");
				end = array[1];
				array = end.split(")");
				numbers = array[0];
				numberArray = numbers.split(" ");
				x = numberArray[0];
				y = numberArray[1];
				
				thisparent = treemap;
				thisparent.appendChild(node);

				// put new plot in Array
				siblings = thisparent.childNodes;
				nodeindoc = siblings.item((siblings.length)-1);
				nodeindoc.setAttribute('x', x);
				nodeindoc.setAttribute('y', y);
				plots.push(nodeindoc);
			}
			else {
				alert("could not get data from $EP::Config::h->{CORE_HREF}/ep_get_genes_from_cluster.pl");
			}
		} );
	}

	function removePNGs(){
		if (plots.length>0){
			for (i = 0; i<plots.length; i++){
				plotnode = plots[i];
				plotparent = plotnode.getParentNode();
				plotparent.removeChild(plotnode);
			}
			plots = new Array();
		}
	}

	////////////
	// ZOOM
	////////////
	
	function zoom_into_box(evt){
		more(evt);
		object = evt.target;
		w = object.getAttribute('width');
		h = object.getAttribute('height');
		x = object.getAttribute('x');
		y = object.getAttribute('y');
		vB = x+" "+y+" "+w+" "+h;
		treemap.setAttribute('viewBox', vB );
	}
	
	function zoom_reset(evt){
		removePNGs();
		treemap.setAttribute('viewBox','$xposition $yposition $width $height');
		current_depth = parseInt(treemap.getAttribute('current_depth'));
		depth         = parseInt(treemap.getAttribute('depth'));
		object        = document.getElementById("d"+current_depth);
		object.setAttribute('display','inherit');
		treemap.setAttribute('current_depth', depth);
	}
	
	function reset_view_box(evt){
		treemap.setAttribute('viewBox','$xposition $yposition $width $height');
	}
	
	function set_detail_level(evt, new_depth){
		removePNGs();
		unselectAll();
		current_depth = parseInt(treemap.getAttribute('current_depth'));
		depth         = parseInt(treemap.getAttribute('depth'));
		object_d_old  = svg.getElementById("d" + current_depth);
		
		if ( (0 <= (new_depth)) && ((new_depth) <= depth))  {
			object_d_old.setAttribute('display','inherit');
			object_d_new = document.getElementById("d"+new_depth);
			object_d_new.setAttribute('display','none');
			treemap.setAttribute('current_depth', new_depth);
		}
		else{
			alert("Error: going out of bounds with "+new_depth+" not between 0 and "+depth);
		}
	}
	

	//////////////////////////////
	// SELECTION AND HIGHLIHTING
	//////////////////////////////
	
	function select(evt){
		box = evt.target;
		selectedClusters.push(box);
		selectedClustersStrokes.push(box.getAttribute('stroke'));
		box.setAttribute('stroke',"red");
		return box.getAttribute('id');
	}
	
	function unselectAll(){
		for (i = 0; i<selectedClusters.length; i++){
			cluster = selectedClusters[i];
			stroke  = selectedClustersStrokes[i];
			cluster.setAttribute('stroke',stroke);
		}
		selectedClusters        = new Array();
		selectedClustersStrokes = new Array();
		annotLink.setAttribute('xlink:href', "");
		annot.setText("");
	}

__JS__
	return $svg1;
}

sub goColor{
	
	my $pvalue = shift;
	my $color;

	if ($pvalue<= 1e-10){
		$color = "red";
	}
	elsif ($pvalue<= 1e-8){
		$color = "orange";
	}
	elsif ($pvalue<= 1e-6){
		$color = "yellow";
	}
	elsif ($pvalue<= 1e-4){
		$color = "#00ff33";
	}
	elsif ($pvalue<= 0.01){
		$color = "#7fffff";
	}
	elsif ($pvalue<= 0.05){
		$color = "#5099ff";
	}
	else {
		$color = "#ebc79e";
	}
	return $color;
}

#####################
# used for treewalk #
#####################
sub nodes_in_subtree {
  my ( $leaves ) = $_[0];
  return $leaves * 2 - 1;
}
# 
##############################################
# returns all nodes in the subtree of a node #
##############################################
sub treewalk {
  my $self = shift;
  my ($N) = @_;

  $N = 0 if not $N;

  if ( $self->{TREE_L}->[$N] == 0 ) {
    my $leaf = $self->{TREE_R}->[$N];
    return $leaf - 1;		# subtract 1 because indices should be zero-based
  }

  return ( $self->treewalk ( $self->{TREE_L}->[$N] ),
	   $self->treewalk ( $self->{TREE_R}->[$N] ) );
}

##################################################################################
# returns the color (rgb) array representing the average expression of a cluster #
# in heatmap colors (yellow to blue)                                             #
##################################################################################
sub compute_color{
  my $self = shift;
	my $id   = shift;
	my @id   = split(":", $id);
	my $heatmap_data = $self->{heatmap_data};
	#my $cluster_gene_ids;
  my @cluster_gene_ids;
	if ($#id > 1){
		$id = $id[1].":".$id[2];
		@cluster_gene_ids = sort{ $a <=> $b }(treewalk ( $self, $self->{SUBTREE}->{$id} ));
		#$cluster_gene_ids = $self->{members}{$id};
	}
	else {
		$id = $id[1];
		@cluster_gene_ids = ($id-1); 
		#my @t = ($id-1); 
		#$cluster_gene_ids = \@t; # in case its a leaf
	}
  #my @cluster_gene_ids = sort(@$cluster_gene_ids);
	my $cluster_heatmap_data = $heatmap_data ( :, pdl @cluster_gene_ids );
	my $average = avg $cluster_heatmap_data;
  my ($mean,$rms,$median) = @{$self->{heatmap_data_stats}};
  $rms              = $mean / 10 if $rms->isbad;
	my $lowerBoundary = $median-$rms*3;
	my $upperBoundary = $median+$rms*3;
	
	my @colors;
	if ($average >= $lowerBoundary && $average <= $upperBoundary){
		my $value = ($average-$lowerBoundary)/($upperBoundary-$lowerBoundary);
		if ($value < 0.5){
			@colors = (0, 0, (255*(((1-$value)-0.5)*2)));
		}
		else {
			@colors = ((255*(($value-0.5)*2)), (255*(($value-0.5)*2)), 0);
		}
	}
	else {
		@colors = ((255/2), (255/2), (255/2));
	}
	#warn "colors ".join(" ", @colors);
	return \@colors;
}

##################
# sub get go ann #
##################
sub initGO {
  my $self = shift;

  select ( STDERR );

  $self->{category} = "process";
  $self->{aspect} = uc(substr($self->{category}, 0, 1));

  if ( !exists( $EP::Config::h->{SPECIES_METADATA}{$self->{species}}{annotationFile} ) ) {
      # No annotation file present for current dataset species
      throw EP::Common::Error ( -text => "No Gene Ontologies present for selected dataset, species " . $self->{species} );
  }

  # obtain the general ontology structure file
#  $self->{session}->status ( "Reading Ontology Tree ..." );
  my $ontology_file = "$EP::Config::h->{EPNG_GODATA_PATH}/$self->{category}.ontology";
  if ( -f $ontology_file ) {
    if ( -f "$ontology_file.obj" &&
	 -M "$ontology_file.obj" < -M $ontology_file ) {
      # serialized objects should be there and newer than ontology file in case of updates
      warn "Reading from stored file" ;
      $self->{ontologyProvider} = GO::OntologyProvider::OntologyParser->new ( objectFile => "$ontology_file.obj" );
    } else {
      warn "Serializing ontology to $ontology_file.obj..." ;
      $self->{ontologyProvider} = GO::OntologyProvider::OntologyParser->new ( ontologyFile => $ontology_file );
      $self->{ontologyProvider}->serializeToDisk ( filename => "$ontology_file.obj" );
    }
  } else {
    die "Could not find ontology file for " . $self->{category};
  }

  # obtain the mapping file for this species
#  $self->{session}->status ( "Reading Annotation Mappings ..." );
  my $annotation_file = $EP::Config::h->{EPNG_GODATA_PATH} . "/" . $EP::Config::h->{SPECIES_METADATA}->{$self->{species}}->{annotationFile};
  if ( -f $annotation_file ) {
      if ( -f "$annotation_file.obj" &&
	 -M "$annotation_file.obj" < -M $annotation_file ) {
	# serialized objects should be there and newer than annotation file in case of updates
      warn "Reading from stored file" ;
      $self->{annotationProvider} = GO::AnnotationProvider::AnnotationParser->new ( objectFile => "$annotation_file.obj" );
    } else {
      warn "Serializing annotation to $annotation_file.obj..." ;
      $self->{annotationProvider} = GO::AnnotationProvider::AnnotationParser->new ( annotationFile => $annotation_file );
      $self->{annotationProvider}->serializeToDisk ( filename => "$annotation_file.obj" );
    }
  } else {
    die "Could not find the annotation file for species: " . $self->{species};
  }

  # Create the background distribution set, the currently selected dataset
  # GO TermFinder wants completely unambiguous gene names for the population input
  my (@ambiguous, @population, @notFound);
  my @genes = @{ $self->{root_dataset}->data()->map_gene_ids_for_GO };

  CategorizeGenes ( annotation  => $self->{annotationProvider},
		    genes       => \@genes,
		    ambiguous   => \@ambiguous,
		    unambiguous => \@population,
		    notFound    => \@notFound);

  die "Couldn't form a background set of genes for GO annotation: check that you have appropriate identifiers available." if scalar @population == 0;

  warn "Background initialization: " . scalar @genes . " genes total, " . scalar @ambiguous . " ambiguous, " . scalar @notFound . " not found.";

  $self->{termFinder} = GO::TermFinder->new( annotationProvider => $self->{annotationProvider},
					     ontologyProvider => $self->{ontologyProvider},
					     population => \@population,
					     aspect => $self->{aspect} );

  return;
}

##################
# sub get go ann #
##################
sub getGO{

	my $self = shift;
	my $ids   = shift;
	my @ids   = split(":", $ids);

	my @cluster_gene_ids;
	if ($#ids > 1){
		$ids = $ids[1].":".$ids[2];
		@cluster_gene_ids = sort{ $a <=> $b }(treewalk ( $self, $self->{SUBTREE}->{$ids} ));
	}
	else {
		$ids = $ids[1];
		@cluster_gene_ids = ($ids-1);
	}
	my $treemap_id = shift (@ids);

	my $species            = $self->{species};
	my $rows               = $self->{rows};
	

	##############
	# GET GO !!!
	my $returnString = "";
	my $genes = $self->{dataset}->data->map_gene_ids_for_GO ( \@cluster_gene_ids );
	
	my (@list, @notFound, @ambiguous, @unambiguous);

 	CategorizeGenes ( annotation  => $self->{annotationProvider},
 		genes       => $genes,
 		ambiguous   => \@ambiguous,
 		unambiguous => \@list,
 		notFound    => \@notFound);

	my $termFinder = $self->{termFinder};
#	foreach my $termFinder ($termFinderP, $termFinderC, $termFinderF){
		
		my @pvalues    = $termFinder->findTerms(genes=>\@list, correction => "none", calculateFDR => 1 );
		my $hypothesis = 1;
		my $shown      = 1; # how many most significant are shown?
		my $cutoff = 0.05;
		my $significantOnes = "";
		$significantOnes .= $termFinder->aspect."- ";

		# sort by corr pvalue to get the ones with the highest corr pvalue first
		my @spvalues = sort { $a->{PVALUE} <=> $b->{PVALUE} } @pvalues;
		
		foreach my $pvalue (@spvalues){
			last if ($hypothesis > $shown);
			next if ($pvalue->{PVALUE} > $cutoff);
			
			# create html formatted links for nodes and genes
			my $goId = $pvalue->{NODE}->goid;
			my $nodeUrl = $EP::Config::h->{SPECIES_METADATA}->{$species}->{nodeUrl};
			$nodeUrl =~ s/<REPLACE_THIS>/$goId/;
	
			my $numGenes = 0;
			foreach my $gene (keys %{$pvalue->{ANNOTATED_GENES}} ) {
				my $geneUrl = $EP::Config::h->{SPECIES_METADATA}->{$species}->{geneUrl};
				$geneUrl =~ s/<REPLACE_THIS>/$pvalue->{ANNOTATED_GENES}{$gene}/;
				$pvalue->{ANNOTATED_GENES}->{$gene} = "<a href=$geneUrl TARGET=_NEW>$pvalue->{ANNOTATED_GENES}{$gene}</a>";
				$numGenes++;
			}
			$significantOnes .= $pvalue->{NODE}->term.":".goColor($pvalue->{PVALUE})."\n";
			$hypothesis++;
		}
	
		# if they had no significant P-values

		if ($hypothesis == 1){
				#$returnString .= "No terms were found for this aspect with a corrected P-value <= $cutoff.\n";
		}
		else {
				$returnString .= $significantOnes;
		}
		#$returnString .= "\n";
#	}
	return $returnString;
}

###############################################################################
# builds the boxes according to the cluster data and returns them in an array #
###############################################################################
sub build_treemap{
  my $self      = shift;
	my $xposition = shift;
	my $yposition = shift;
	my $width     = shift;
	my $height    = shift;
	my @data      = @{ $self->getData() };
	
	my $line_ref              = $data[0];
	my ($LH, $RH, $LHS, $RHS) = @$line_ref;

  my $treeID = $self->get_uuid();

  my $BOUND_WIDTH = $self->{boundwidth};
	my $rotated           = 1;
	my $depth             = -1;

	my %PARENTS     = ();
	my @readyToDraw = (); # an AoA for the computed bounds

	$xposition += (2 * $BOUND_WIDTH);
	$width     -= (4 * $BOUND_WIDTH);
	$yposition += (2 * $BOUND_WIDTH);
	$height    -= (4 * $BOUND_WIDTH);

	my $line0_ref = $data[0];
	my ($LH0, $RH0, $LHS0, $RHS0) = @$line0_ref;
	my @box = ($xposition, $yposition, $width, $height, $rotated, $depth, "#0000FF", $LHS0+$RHS0);

	$PARENTS{1} = \@box;

	# Loop through lines of data
	for my $i (0 .. $#data){
		my $line_ref = $data[$i];
		my ($LH, $RH, $LHS, $RHS) = @$line_ref;
		my $ref_Box;

		# get and delete from %PARENTS

		if (exists($PARENTS{$LH})){

			$ref_Box = $PARENTS{$LH};
			delete($PARENTS{$LH});
		}
		elsif (exists($PARENTS{$RH})){

			$ref_Box = $PARENTS{$RH};
			delete($PARENTS{$RH});
		}
		else {
			die "THIS SHOULD NOT HAPPEN: $LH and $RH not found in \%PARENTS\n";
		}
	
		my @box = @$ref_Box;
		my ($x, $y, $w, $h, $rotated, $depth, $fill, $size) = @box;
		
		push @box, $self->get_uuid().":$data[$i][0]:$data[$i][1]";
		push @readyToDraw, [ @box ]; 


		# narrow parent bounds to compute the child boxes 

		if ($w >= 2*$BOUND_WIDTH+1){# to get rid of drawing artifacts in big trees
			$x += (1 * $BOUND_WIDTH);
			$w -= (2 * $BOUND_WIDTH);
		}
		if ($h >= (2*$BOUND_WIDTH+1)) {# to get rid of drawing artifacts in big trees
			$y += (1 * $BOUND_WIDTH);
			$h -= (2 * $BOUND_WIDTH);
		}

		my $totalS = $LHS + $RHS;
		$rotated   = !$rotated; # rotate relating to the mama cell
		$depth    += 1;         # increase depth relating to the mama cell
		# Compute Boxes
		
		my @LeftBox  = ();
		my @RightBox = ();

		my ($xL, $xLR, $wL, $wLR);
		my ($xR,       $wR,);
		my ($X, $Y, $W, $H);

		if ($rotated){
			($X, $Y, $W, $H) = ($y, $x, $h, $w);
		}
		else {  # if rotated
			($X, $Y, $W, $H) = ($x, $y, $w, $h);
		}

		# width
		
		$xLR = $Y;
		$wLR = $H;
		if    ($H < 1){# otherwise they are not shown at all
			$wLR = 1;
		}
		elsif ($H > 2 * $BOUND_WIDTH + 1){
			$wLR -= 2 * $BOUND_WIDTH;
			$xLR += $BOUND_WIDTH;
		}

		# height
		
		$wL = round($W * ($LHS/$totalS));
		$wR = $W - $wL;
		$xL = $X;
		$xR = $X;

		if    (($wL >  2 * $BOUND_WIDTH) && ($wR >  2 * $BOUND_WIDTH)){ # both are big
			$wL -= 2   * $BOUND_WIDTH;
			$wR -= 2   * $BOUND_WIDTH;
			$xL += $BOUND_WIDTH;	
			$xR  = $xL + $wL + 2 * $BOUND_WIDTH;
		}
		elsif (($wL >  4 * $BOUND_WIDTH + 1) && ($wR <= 2 * $BOUND_WIDTH)){ # only L is big
			# steal from L
			$wR  = 1; 
			$wL  = $W  - $wR - 4 * $BOUND_WIDTH;
			$xL += $BOUND_WIDTH;
			$xR  = $xL + $wL + 2 * $BOUND_WIDTH;
		}
		elsif (($wL <= 2 * $BOUND_WIDTH) && ($wR >  4 * $BOUND_WIDTH + 1)){ # only R is big
			# steal from R
			$wL  = 1; 
			$wR  = $W  - $wL - 4 * $BOUND_WIDTH;
			$xL += $BOUND_WIDTH;
			$xR  = $xL + $wL + 2 * $BOUND_WIDTH;
		}
		else { # none is big
			$wL = $W;
			$wR = $W;
		}

		if ($rotated){
			# Left -> upper, right -> lower
			@LeftBox  = ($xLR, $xL, $wLR, $wL);
			@RightBox = ($xLR, $xR, $wLR, $wR);
		}
		else {
			@LeftBox  = ($xL, $xLR, $wL, $wLR);
			@RightBox = ($xR, $xLR, $wR, $wLR);
		}

		push(@LeftBox,  ($rotated, $depth, "#0000FF", $LHS));
		push(@RightBox, ($rotated, $depth, "#00FF00", $RHS));

		# Store parent bounds for the children: x y w h depth fill
		# draw if leaf
		
		# Left
		
		if (exists($PARENTS{$LH})) {
			die "THIS SHOULD NOT HAPPEN: $LH ALREADY IN PARENT HASH IN ROUND $i";
		}
		elsif ($LHS == 1){ # leaf - put not in %PARENTS, but draw
			push @LeftBox, $self->get_uuid().":$LH" ; # just leaf id

			# Store computed boxes: x y w h $rotated depth fill id
			push @readyToDraw, [ @LeftBox ];
		}
		else{
			$PARENTS{$LH} = \@LeftBox;
		}

		# Right
		
		if (exists($PARENTS{$RH})) {
			die "THIS SHOULD NOT HAPPEN: $RH ALREADY IN PARENT HASH IN ROUND $i\n";
		}
		elsif ($RHS == 1){ # leaf - put not in %PARENTS, but draw
			push @RightBox, $self->get_uuid().":$RH" ; # just leaf id

			# Store computed boxes: x y w h $rotated depth fill id
			push @readyToDraw, [ @RightBox ];
		}
		else{
			$PARENTS{$RH} = \@RightBox;
		}
	}

	while ( my ($k,$v) = each %PARENTS ) {
		my @a = @$v;
		die "THIS SHOULD NOT HAPPEN: $k => $v STILL LEFT IN PARENT HASH, THIS IS BOX (".join(", ",@a).")\n"; 
	}
	return \@readyToDraw;
}

###################################
# used for reading in of the tree #
###################################
sub read_tree_data_from_file {
  my ($file) = shift;

  my @DATA;

  open ( CLUSTER, $file ) or die "Couldn't open the hierarchical clustering file $file: $!\n";
  while (<CLUSTER>) {
    next if /^\s*#/;

    # From the original clustering output
    my @data_row = split /\s+/;
    push @DATA, \@data_row;
  }
  close CLUSTER;

  return \@DATA;
}

############################################################################
# reads in tree data and builds a representation on which the treewalk can #
# operate                                                                  #
############################################################################
sub read_in_tree {
  my $self = shift;
  my ( $tree_data ) = @_;
  my %LL;
  my @TREEDOWN;
  my $TREENODE = 0;

  my (@TREE_L, @TREE_R, %DIST, %SUBTREE, @ORIG_TREE);

  my $DATA;
  $DATA = read_tree_data_from_file ( $tree_data ) if -f $tree_data;
  $DATA = $tree_data if ref ( $tree_data ) eq "ARRAY";
  $DATA = $tree_data->{data} if ref ( $tree_data ) eq "HASH" and exists $tree_data->{data};

  die "Couldn't read in tree data $tree_data!" if not $DATA;

  my @data = @{$DATA};
  my $maxDist = 0;
  my $minDist = 1;

  #normalize data such that the distance is between 0 and 1 so drawing will occur within the borders of the object
  for ( my $a = 0; $a < scalar( @data ); $a++){
    $maxDist = $data[ $a ][ 4 ] if $data[ $a ][ 4 ] > $maxDist;
    $minDist = $data[ $a ][ 4 ] if $data[ $a ][ 4 ] < $minDist && $data[ $a ][ 4 ] != 0;
  }

  for ( my $a = 0; $a < scalar( @data ); $a++){
    $data[ $a ][ 4 ] = $minDist / 2                 if $data[ $a ][ 4 ] <= 0;
    $data[ $a ][ 4 ] = $data[ $a ][ 4 ] / $maxDist if $maxDist != 0;
  }

  foreach my $data_row ( @{$DATA} ) {
    # From the original clustering output
    my ( $LH, $RH, $LHS, $RHS, $D ) = @$data_row;

    $TREEDOWN[1] = [ 0, $LHS + $RHS ] if not exists $TREEDOWN[1];

    # @TREEDOWN  - how much space was reserved for subtree starting with that node
    my ( $TREENODE, $count ) = @{$TREEDOWN[$LH]};

    # $LHTREE, $RHTREE, $TREENODE -  tree-node id's on tree structure
    my $LHTREE = $TREENODE + 1;
    my $RHTREE = $TREENODE + nodes_in_subtree($LHS) + 1;

    ( $TREE_L   [$TREENODE], $TREE_R   [$TREENODE] ) = ( $LHTREE,           $RHTREE);
    ( $ORIG_TREE[$LHTREE  ], $ORIG_TREE[$RHTREE  ] ) = ( $LH,               $RH );
    ( $TREEDOWN [$LH      ], $TREEDOWN [$RH      ] ) = ( [ $LHTREE, $LHS ], [ $RHTREE, $RHS ] );

    $DIST{"$LH:$RH"}    = $D;
    $SUBTREE{"$LH:$RH"} = $TREENODE;

    # left leaf
    ( $TREE_L[$LHTREE], $TREE_R[$LHTREE] ) = ( 0, $LH ) if $LHS == 1;

    # right leaf
    ( $TREE_L[$RHTREE], $TREE_R[$RHTREE] ) = ( 0, $RH ) if $RHS == 1;
  }
  $self->{nr_leaves} = $$DATA[0][2] + $$DATA[0][3];
  $self->{TREE_L}    = \@TREE_L;
  $self->{TREE_R}    = \@TREE_R;
  $self->{DIST}      = \%DIST;
  $self->{SUBTREE}   = \%SUBTREE;
  $self->{ORIG_TREE} = \@ORIG_TREE;

	$self->{data}       = $DATA;
	$self->{initdepth}  = 1;
	$self->{boundwidth} = 1;

}

sub round {
    my($number) = shift;
    return int($number + .5);
}
















####################
#                  #
#   ControlPanel   #
#                  #
####################

package EP::Visualization::TreemapGraphic::ControlPanel;

use base 'EP::Visualization::Graphics';


############################################
# sets data of the control panel, that is: #
#  - the treemap to control                #
############################################
sub setData {
	my $self = shift;
	my %params = @_;

	die "What should I control?" if not $params{control};

	$self->{control} = $params{control};
	return;
}

############################################################
# returns string identifying the object as a control panel #
############################################################
sub getType {
  return "treemap control panel";
}


#######################
# returns panel width #
#######################
sub getWidth {
	my $self = shift;
	return 200;
}

########################
# returns panel height #
########################
sub getHeight {
	my $self = shift;
	return $self->{control}->getHeight();
}


##########################################################
# produces the SVG elements of the control panel object. #
##########################################################
sub to_svg {
	my $self = shift;
	my ( $svg, $height, $width, $xposition, $yposition ) = @_;
	
	# Panel
 
	my $panel = $svg->group ( transform => "translate($xposition $yposition)", 
													id => 'panel');
	$panel->rect ( 'x' => 0, 
								'y' => 0, 
								height => $height, 
								width => $width, 
								fill => 'none', 
								stroke => 'black' );

	# slider
	my $sliderAreaHeight  = (3/24)*$height;
	$self->buildSlider($svg, $sliderAreaHeight, $width, $xposition, $yposition);


	# some vars coming from the slider
	my $lowerBound    = $self->{lowerBound};
	my $upperBound    = $self->{upperBound};
	my $steps         = $self->{steps};
	my $stepsize      = $self->{stepsize};
	my $slideto       = $self->{control}->get_init_depth();
	my $thumbposition = $lowerBound + ($slideto * $stepsize);


	# buttonbar
	
	my $buttonbarAreaHeight  = (5/24)*$height;
	my $buttonbarAreaY       = $sliderAreaHeight;
	$self->buildButtonbar($svg, $buttonbarAreaHeight, $width, $xposition, $buttonbarAreaY);


	# textbox
	
	my $textboxAreaHeight  = (12/24)*$height;
	my $textboxAreaY       = $sliderAreaHeight+$buttonbarAreaHeight;
	$self->buildTextbox($svg, $textboxAreaHeight, $width, $xposition, $textboxAreaY);


	# small tree
	
	my $treeAreaHeight  = (4/24)*$height;
	my $treeAreaY       = $sliderAreaHeight + $buttonbarAreaHeight + $textboxAreaHeight;
	$self->buildTree($svg, $treeAreaHeight, $width, $xposition, $treeAreaY);


	# Script    

	$svg->script('type'=>"text/ecmascript")->cdata(<<__JS__);
	var svg         = document;
	var treemap     = svg.getElementById("treemap");
	var slider      = svg.getElementById("slider");
	var thumb       = svg.getElementById("thumb");
	var cuttingline = svg.getElementById("cuttingline");
	lowerBound      = $lowerBound;
	upperBound      = $upperBound;

	var svgui; 

	///////////////
	// FUNCTIONS
	///////////////

	function init(){              
		svgui = importModule("svgui");
		svgui.initUI();
		annot.setText("");
	}

	function SliderClick(event) {
		value = event.getClientX() - parseFloat(slider.getAttribute("xoffset")) ;
		//alert("client x "+event.getClientX());
		//alert("xoffset  "+slider.getAttribute("xoffset"));
		//alert("stepsize "+$stepsize);

		slideto = Math.round((value - lowerBound)/$stepsize);
		//alert("wir wollen nach "+slideto);
		slide_to(event, slideto);
	}

	function slide_to(event, slideto) {
		if (slideto < 0) {
			slideto = 0;
		}
		if (slideto > $steps) {
			slideto = $steps;
		}
		value      = lowerBound + (slideto * $stepsize);
		thumb.setAttribute("transform", "translate(" + value + " 0)");
		cuttingline.setAttribute("transform", "translate(" + value + " 0)");
		set_detail_level(evt, slideto);
	}

	function less(evt){
		current_depth = parseInt(treemap.getAttribute('current_depth'));
		slide_to(evt, current_depth - 1);
	}

	function more(evt){
		current_depth = parseInt(treemap.getAttribute('current_depth'));
		slide_to(evt, current_depth + 1);
	}

	function reset(evt){
		reset_view_box(evt);
		depth        = parseInt(treemap.getAttribute('depth'));
		slide_to(evt, depth);
	}

__JS__
	return $svg;
}


##############################################
# used to build the slider part of the panel #
##############################################
sub buildSlider {
	my $self = shift;
	my ( $svg, $height, $width, $xposition, $yposition ) = @_;
	
	my $bigMargin         =  10;
	my $SLIDERPANELWIDTH  =  $width  - (2 * $bigMargin);
	my $SLIDERPANELHEIGHT = ($height - (3 * $bigMargin))/2;
	$SLIDERPANELHEIGHT = 19;
	my $xOffset           =  $xposition + $bigMargin - 93;
	
	my $steps       = $self->{control}->get_depth();
	my $stepsize    = $SLIDERPANELWIDTH / ($steps + 1); # plus one for nice margin
	my $smallMargin = $stepsize / 2;
	my $sliderwidth = $SLIDERPANELWIDTH - 2 * $smallMargin;
	
	my $thumbwidth = 5;
	my $lowerBound = $smallMargin - $thumbwidth;
	my $upperBound = $smallMargin + $sliderwidth - $thumbwidth;
	
	my $sliderArea = $svg->group('transform' => "translate($xposition $yposition)");
	$sliderArea->rect ( 'x' => 0, 
								'y'    => 0, 
								width  => $width, 
								height => $height+1, 
								fill=>"#D7C2C7" );
	my $slider = $sliderArea->group ( 'id' => "slider", 
					'transform' => "translate($bigMargin $bigMargin)", 
					'xoffset'   => $xOffset, # PANEL X + SLIDER X TODO
					#'onmousedown' => 'SliderDown(evt)', 'onmouseup' => 'SliderUp(evt)', 
					#'onmousemove' => 'SliderMove(evt)', 
					'onclick'   => 'SliderClick(evt)'); 
	$slider->rect ( 'x' => 0, 
								'y'    => 0, 
								width  => $SLIDERPANELWIDTH, 
								height => $SLIDERPANELHEIGHT, 
								fill   => '#c0c0c0',
								stroke => "black");

	# cool people count from  0: i. e. depth=9 => 10 levels, 10 marks on slider 
	# TODO: is this cool? As the 1st one isnt used anyway...


	# slider marks

	my $d = "";
	my $x = $smallMargin;
	for my $i(0 ..  $steps){
		$d .= "M$x,".( $SLIDERPANELHEIGHT - $SLIDERPANELHEIGHT / 5 )." $x,$SLIDERPANELHEIGHT";
		$x += $stepsize;
	}

	$slider->line ( style => 'stroke:black;stroke-width:2;', 'x1' => 5, 'y1' => $SLIDERPANELHEIGHT/3, 
					'x2' => $SLIDERPANELWIDTH - 5, 'y2' => $SLIDERPANELHEIGHT / 3);
	$slider->line ( style => 'stroke:black;stroke-width:2;', 'x1' => 5, 'y1' => $SLIDERPANELHEIGHT/3, 
					'x2' => $SLIDERPANELWIDTH - 5, 'y2' => ( ($SLIDERPANELHEIGHT / 3) + 2 )  );

	$slider->path ( style => 'stroke:black;fill:none;', d => "$d" );


	# Thumb

	my $slideto       = $self->{control}->get_init_depth();
	my $thumbposition = $lowerBound + ($slideto * $stepsize);

	my $thumb = $slider->group ( id => "thumb", transform => "translate($thumbposition 0)" );
	$thumb->path ( style => 'stroke:none;fill:red;',   d => 'M1,2 1,12 5,15 9,12 9,2 Z' );
	$thumb->path ( style => 'stroke:white;fill:none;', d => 'M9,1 1,1 1,11' );
	$thumb->path ( style => 'stroke:black;fill:none;', d => 'M5,16 10,12 10,2' );

	# more & less buttons TODO: nest them 
	my $buttonMargin  = 4; 
	my $buttonwidth   = ($SLIDERPANELWIDTH - 5*$buttonMargin)/6;
	my $buttonheight  = $SLIDERPANELHEIGHT;
	my $middle        = 0;
	my @action        = ("slide_to(evt, 0)", "less(evt)", "more(evt)", "slide_to(evt, $steps)");
	my $plus_path  = 'M '.((1/3)*$buttonwidth).' '.((1/3)*$buttonheight).
									' L '.((2/3)*$buttonwidth).' '.((1/2)*$buttonheight).
									' L '.((1/3)*$buttonwidth).' '.((2/3)*$buttonheight).' z';
	my $minus_path = 'M '.((2/3)*$buttonwidth).' '.((1/3)*$buttonheight).
									' L '.((1/3)*$buttonwidth).' '.((1/2)*$buttonheight).
									' L '.((2/3)*$buttonwidth).' '.((2/3)*$buttonheight).' z';

	for my $button_number (0..3){
		my $buttongroup = $sliderArea->group('transform' => 
																	"translate(".($bigMargin+ $button_number*($buttonwidth+$buttonMargin) + $middle) . " "
																	.(2*$bigMargin+$buttonheight).")",
																	'onclick' => $action[$button_number] );

		$buttongroup -> rect ( 'x' => 0, 
													'y'    => 0,
													width  => $buttonwidth, 
													height => $buttonheight, 
													stroke => "black", 
													fill   => "lightgrey" );

		# add button descriptions
		my $d;
		if ($button_number <= 1 ){
			$d = $minus_path;
		}
		else{
			$d = $plus_path;
		}
		if ($button_number == 0 || $button_number == 3){
			my $labelgroup = $buttongroup->group(transform => 
												"translate(".(-(1/6)*$buttonwidth)." 0)");
			$labelgroup->path ( stroke => "black", fill => "black", d => $d);
			$labelgroup = $buttongroup->group(transform => 
												"translate(".((1/6)*$buttonwidth)." 0)");
			$labelgroup->path ( stroke => "black", 
			fill => "black", d => $d);
		}
		else{
			$buttongroup->path ( stroke => "black", 
													fill => "black", 
													d    => $d);
		}

		$middle = 2*($buttonwidth+$buttonMargin) if ($button_number == 1);
		$button_number++;
	}

	$self->{lowerBound}   = $lowerBound;
	$self->{upperBound}   = $upperBound;
	$self->{steps}        = $steps;
	$self->{stepsize}     = $stepsize;
	$self->{buttonwidth}  = $buttonwidth;
	$self->{buttonheight} = $buttonheight;
	return $svg;
}

#############################################
# used to build the button bar on the panel #
#############################################
sub buildButtonbar{
	my $self = shift;
	my ( $svg, $height, $width, $xposition, $yposition ) = @_;

	my $margin      = 10;
	my $smallMargin = 5;

	my $buttonwidth  = $self->{buttonwidth};
	$buttonwidth = ($width - (2*$margin+$smallMargin))/2;
	my $buttonheight = $self->{buttonheight};
	$buttonwidth  = 19;
	$buttonheight = 19;
	my $html_href = $EP::Config::h->{HTML_HREF};
	
	my $treeArea = $svg->group('transform' => "translate($xposition $yposition)");
	$treeArea->rect ( 'x' => 0, 
								'y'    => 0, 
								width  => $width, 
								height => $height+1, 
								fill=>"#D7C2C7");

	# the buttons
	my $toggleButtonGroup = $treeArea->group('transform' => "translate($margin $margin)",
								onclick => 'togglePNG()', 'id' => 'toggle');
	
	$toggleButtonGroup->image ( 'x' => 0, 
													'y'      => 0, 
													'width'  => $buttonwidth,
													'height' => $buttonheight,
													'xlink:href' => "$html_href/static/images/hm.png");
	$toggleButtonGroup->text("x"          => $buttonwidth+$smallMargin,
										"y"            => (2/3)*$buttonheight,
										"text-anchor"  => "left",
										"fill"         => "black" )->cdata ( "Heatmap or Lineplot?" );
	
	my $saveButtonGroup = $treeArea->group('transform' => "translate($margin ".($margin+$buttonheight+$smallMargin).")",
								onclick => 'save_and_prompt()');
	$saveButtonGroup->image ( 'x' => 0, 
													'y'      => 0, 
													'width'  => $buttonwidth,
													'height' => $buttonheight,
													'xlink:href' => "$html_href/static/images/gui_icons/cluster.png");
	$saveButtonGroup->text("x"          => $buttonwidth+$smallMargin,
										"y"            => (2/3)*$buttonheight,
										"text-anchor"  => "left",
										"fill"         => "black", 
										"alignment-baseline"=>"middle")->cdata ( "Save selected Cluster" );
	
	my $annotButtonGroup = $treeArea->group('transform' => "translate($margin ".($margin+2*($buttonheight+$smallMargin)).")");
	
	my $anLink = $annotButtonGroup->a('id'=>'annotLink', 'xlink:href'=>"", 'xlink:show' => 'new', 'target' => "_ ");
	$anLink->image ( 'x' => 0,
													'y'      => 0, 
													'width'  => $buttonwidth,
													'height' => $buttonheight,
													'xlink:href' => "$html_href/static/images/gui_icons/rows.gif");
	$annotButtonGroup->text("x"          => $buttonwidth+$smallMargin,
										"y"            => (2/3)*$buttonheight,
										"text-anchor"  => "left",
										"fill"         => "black", 
										"alignment-baseline"=>"middle")->cdata ( "View annotations" );

	my $goButtonGroup = $treeArea->group('transform' => "translate($margin ".($margin+3*($buttonheight+$smallMargin)).")");
	
	my $goLink = $goButtonGroup->a('id'=>'goLink', 'xlink:href'=>"", 'xlink:show' => 'new', 'target' => "_ ");
	$goLink->image ( 'x' => 0,
													'y'      => 0, 
													'width'  => $buttonwidth,
													'height' => $buttonheight,
													'xlink:href' => "$html_href/static/images/gui_icons/rows.gif");
	$goButtonGroup->text("x"          => $buttonwidth+$smallMargin,
										"y"            => (2/3)*$buttonheight,
										"text-anchor"  => "left",
										"fill"         => "black", 
										"alignment-baseline"=>"middle")->cdata ( "View GO ann." );
	return $svg;

}

########################################################
# used to build the textbox displaying the annotations #
########################################################
sub buildTextbox{
	my $self = shift;
	my ( $svg, $height, $width, $xposition, $yposition ) = @_;
	$svg->script('type'=>"text/ecmascript",
					'xlink:href'=>"$EP::Config::h->{HTML_HREF}/static/jsolait/init.js");
	$svg->script('type'=>"text/ecmascript",
					'xlink:href'=>"$EP::Config::h->{HTML_HREF}/static/js/svgui.js");
	

	my $MARGIN = 5;
	my $textboxArea = $svg->group('transform' => "translate($xposition $yposition)");

	$textboxArea->rect ( 'x' => 0, 
								'y'    => 0, 
								width  => $width, 
								height => $height+1, 
								fill=>"#D7C2C7");
	
	# skin 
	my $textbox_svg = $textboxArea->svg('x' => 0, 'y' => 0,
                        'width'=>$width, 'height'=>$height,
                        'xmlns:ui'=>"http://jan.kollhof.net/svgui",
                        'onload'=>"init()",
                        zoomAndPan=>"disable");
	my $defs = $textbox_svg->defs();
	my $skin = $defs->tag("skin", -namespace=>'ui', "ui:compElem"=>'textBox',
							'ui:componentClass'=>"TextBox");
	# actual textfield
	# svg sets width and height of textfield
	my $innersvg = $skin->svg('x'=>0,
													'y'=>$MARGIN,
													'width'=>$width-4*$MARGIN,
													'height'=>$height-4*$MARGIN);
	# pos of textfield in frame
	my $g1 = $innersvg->group('transform'=>"translate(12,2)");
		# white part in textfield, influences width of textfield
		$g1->rect('ui:initParam'=>"0", 'x'=>"0", 'y'=>"0", 'width'=>$width-4*$MARGIN, 'height'=>$height-4*$MARGIN,
				 'stroke'=>"steelblue", 'fill'=>"white", 'cursor'=>"text");
	my $g2 = $g1->group(transform=>"translate(2,0)");
		$g2->text('x'=>"0", 'y'=>"0",  'ui:initParam'=>"1", 'xml:space'=>"preserve",
			 'font-family'=>"monospace", 'text-rendering'=>"optimizeSpeed", 'font-size'=>"12");
	my $g3 = $g2->group(transform=>"translate(0,-14)");
	# cursor?
	my $r2 = $g3->rect('ui:initParam'=>"2", width=>"1", height=>"16", y=>"16", fill=>"none");
# 		$r2->animate(attributeName=>"opacity", dur=>"1s", 'values'=>"1;0;1", keyTimes=>"0;0.5;1",
# 				 repeatCount=>"indefinite" );
	# scrollbar?
	my $g4 = $g1->group('transform'=>"translate(505,270)", opacity=>".7");
		$g4->rect('x'=>"0", 'y'=>"-270", width=>"15", height=>$height-4*$MARGIN, rx=>"5", ry=>"5", 
				stroke=>"lightsteelblue", fill=>"aliceblue");
	# scrollbar?
		my $g5 = $g4->group("ui:initParam"=>"3");
			$g5->rect('x'=>"0", 'y'=>"0", width=>"15", height=>"15", rx=>"5", ry=>"5",
					stroke=>"black", fill=>"lightsteelblue");
			$g5->path(d=>"M7.5,5 l5,5 l-10,0 z", fill => "black" );
	# scrollbar?
		my $g6 = $g4->group('ui:initParam'=>"4");
			$g6->rect('x'=>"0", 'y'=>"15", width=>"15", height=>"15", rx=>"5",
						ry=>"5", stroke=>"black", fill=>"lightsteelblue");
			$g6->path(d=>"M7.5,26 l5,-5 l-10,0 z", fill => "black");
	# scrollbar?
	my $g7 = $g1->group(transform=>"translate(505,0)", opacity=>".7");
	my $r6 = $g7->rect('ui:initParam'=>"5", x=>"0", y=>"0", width=>"15", height=>"20", 
		rx=>"5", ry=>"5", stroke=>"black", fill=>"lightsteelblue");
	
	# background and textbox
	
	$textbox_svg->rect('x'=>0, 'y'=>0, width=>$width, 
						height=>$height+1, 
						fill=>"#D7C2C7");
	$textbox_svg->tag("textBox", -namespace=>'ui', "x"=>"$MARGIN", "y"=>"$MARGIN",
						 'ui:name'=>"annot");
	return $svg;

}

##############################################
# used to build the little tree on the panel #
##############################################
sub buildTree{
	my $self = shift;
	my ( $svg, $height, $width, $xposition, $yposition ) = @_;
	my $data = $self->{control}->getData();
	my @data  = @{ $data };

	my $bigMargin   = 10;
	
	my $treeArea = $svg->group('transform' => "translate($xposition $yposition)");
	$treeArea->rect ( 'x' => 0, 
								'y'    => 0, 
								width  => $width, 
								height => $height+1, 
								fill=>"#D7C2C7");
	
	my ($cell_height, $cell_width ) = ( 1, 1 );#swapped bc. its rotated
	$cell_height = (($height-2*$bigMargin) /(scalar @data));
	$cell_width  = (($width -2*$bigMargin) /(scalar @data));

	my $minitree = new EP::Visualization::TreeGraphic ( data => $data,
							 leaf_height => $cell_height, minitree => 1 );#tree_width => 150,
	$minitree -> setOrientation(-90);

	my $tree_svg = $treeArea->svg('x' => 0, 
																'y' => 0,
																'width' =>$width, 
																'height'=>$height);
	$minitree->to_svg($tree_svg , $height-2*$bigMargin, ($width-2*$bigMargin), $bigMargin, $bigMargin );

	# line to show where we are
	
	my $lowerBound    = $self->{lowerBound};
	my $stepsize      = $self->{stepsize};
	my $slideto       = $self->{control}->get_init_depth();
	my $thumbposition = $lowerBound + ($slideto * $stepsize);
	my $cutting_line = $tree_svg->group ( id => "cuttingline", 
									transform => "translate(".($thumbposition)." 0)" );
	$cutting_line->line ( style => 'stroke:red;stroke-width:2;', 'x1' => $bigMargin, 
					'y1' => 0, 
					'x2' => $bigMargin, 
					'y2' => $height);
	return $svg;
}

1;
